#ifndef RTSOCKET_H
#define RTSOCKET_H

#include "RtDefines.h"
#include "RtError.h"
#include "RtInetAddr.h"

class RT_API_EXPORT RT_IPC_SAP
{
public:
	enum { NON_BLOCK = 0 };

	RT_IPC_SAP() : m_Handle(RT_INVALID_HANDLE) { }

	RT_HANDLE GetHandle() const;
	void SetHandle(RT_HANDLE aNew);

	int Enable(int aValue) const ;
	int Disable(int aValue) const ;
	int Control(int aCmd, void *aArg) const;
	
protected:
	RT_HANDLE m_Handle;
};


class RT_API_EXPORT CRtSocketBase : public RT_IPC_SAP
{
protected:
	CRtSocketBase();
	~CRtSocketBase();

public:
	/// Wrapper around the BSD-style <socket> system call (no QoS).
	int Open(int aFamily, int aType, int aProtocol, BOOL aReuseAddr);

	/// Close down the socket handle.
	int Close(RtResult aReason = RT_OK);

	/// Wrapper around the <setsockopt> system call.
	int SetOption(int aLevel, int aOption, const void *aOptval, int aOptlen) const ;

	/// Wrapper around the <getsockopt> system call.
	int GetOption(int aLevel, int aOption, void *aOptval, int *aOptlen) const ;

	/// Return the address of the remotely connected peer (if there is
	/// one), in the referenced <aAddr>.
	int GetRemoteAddr(CRtInetAddr &aAddr) const;

	/// Return the local endpoint address in the referenced <aAddr>.
	int GetLocalAddr(CRtInetAddr &aAddr) const;

	/// Recv an <aLen> byte buffer from the connected socket.
	int Recv(char *aBuf, DWORD aLen, int aFlag = 0) const ;

	/// Recv an <aIov> of size <aCount> from the connected socket.
	int RecvV(iovec aIov[], DWORD aCount) const ;

	/// Send an <aLen> byte buffer to the connected socket.
	int Send(const char *aBuf, DWORD aLen, int aFlag = 0) const ;

	/// Send an <aIov> of size <aCount> from the connected socket.
	int SendV(const iovec aIov[], DWORD aCount) const ;

protected:
	int CloseWriter();
};


class RT_API_EXPORT CRtSocketStream : public CRtSocketBase
{
public:
	CRtSocketStream();
	~CRtSocketStream();

	int Open(BOOL aReuseAddr = FALSE);
	int Open(BOOL aReuseAddr, const CRtInetAddr &aLocal);
//	int Close(RtResult aReason = RT_OK);
//	int CloseWriter();
	int CloseReader();
};

class RT_API_EXPORT CRtSocketDgram : public CRtSocketBase
{
public:
	CRtSocketDgram();
	~CRtSocketDgram();

	int Open(const CRtInetAddr &aLocal);

	int RecvFrom(char *aBuf, 
				 DWORD aLen, 
				 CRtInetAddr &aAddr, 
				 int aFlag = 0) const ;

	int SendTo(const char *aBuf, 
			   DWORD aLen, 
			   const CRtInetAddr &aAddr, 
			   int aFlag = 0) const ;

	int SendVTo(const iovec aIov[], 
				DWORD aCount,
				const CRtInetAddr &aAddr) const ;
};


// inline functions
inline RT_HANDLE RT_IPC_SAP::GetHandle() const 
{
	return m_Handle;
}

inline void RT_IPC_SAP::SetHandle(RT_HANDLE aNew)
{
	RT_ASSERTE(m_Handle == RT_INVALID_HANDLE || aNew == RT_INVALID_HANDLE);
	m_Handle = aNew;
}

inline CRtSocketBase::CRtSocketBase()
{
}

inline CRtSocketBase::~CRtSocketBase()
{
	Close();
}

#ifdef RT_LINUX
	#include <linux/unistd.h>
	#include <linux/types.h>
	#include <sys/sysctl.h>
#endif

inline int CRtSocketBase::SetOption(int aLevel, int aOption, const void *aOptval, int aOptlen) const 
{
#ifdef RT_UNIX
	if (aLevel==SOL_SOCKET && (aOption==SO_SNDBUF || aOption==SO_RCVBUF)) {
		int name[] = {CTL_NET, NET_CORE, NET_CORE_WMEM_MAX};
		if (aOption==SO_RCVBUF)
			name[2] = NET_CORE_RMEM_MAX;
		DWORD oldval = 0;
		size_t oldlen = sizeof(DWORD);
		DWORD newval = *(DWORD*)aOptval;
		size_t newlen = sizeof(DWORD);
		int rv = sysctl(name, 3, &oldval, &oldlen, 0, 0);
		if (rv==-1) {
			RT_WARNING_TRACE("CRtSocketBase::SetOption, get " << ((aOption==SO_SNDBUF) ? "wmem_max" : "rmem_max") << " fail! errno="<<errno);
		}
		else if (oldval<newval) {
			rv = sysctl(name, 3, 0, 0, &newval, newlen);
			if (rv==-1) {
				RT_WARNING_TRACE("CRtSocketBase::SetOption, set " << ((aOption==SO_SNDBUF) ? "wmem_max" : "rmem_max") << " fail! errno="<<errno);
			}
			else {
				RT_INFO_TRACE("CRtSocketBase::SetOption, " << ((aOption==SO_SNDBUF) ? "wmem_max" : "rmem_max") << "="<<newval);
			}
		}
	}
#endif

	int nRet = ::setsockopt((RT_SOCKET)m_Handle, aLevel, aOption, 
#ifdef RT_WIN32
		static_cast<const char*>(aOptval), 
#else // !RT_WIN32
		aOptval,
#endif // RT_WIN32
		aOptlen);

#ifdef RT_WIN32
	if (nRet == SOCKET_ERROR) {
		errno = ::WSAGetLastError();
		nRet = -1;
	}
#endif // RT_WIN32
	return nRet;
}

inline int CRtSocketBase::GetOption(int aLevel, int aOption, void *aOptval, int *aOptlen) const 
{
//	RT_ASSERTE(m_Handle != RT_INVALID_HANDLE);
	int nRet = ::getsockopt((RT_SOCKET)m_Handle, aLevel, aOption, 
#ifdef RT_WIN32
		static_cast<char*>(aOptval), 
		aOptlen
#else // !RT_WIN32
		aOptval,
		reinterpret_cast<socklen_t*>(aOptlen)
#endif // RT_WIN32
		);

#ifdef RT_WIN32
	if (nRet == SOCKET_ERROR) {
		errno = ::WSAGetLastError();
		nRet = -1;
	}
#endif // RT_WIN32
	return nRet;
}

inline int CRtSocketBase::Recv(char *aBuf, DWORD aLen, int aFlag) const
{
//	RT_ASSERTE(m_Handle != RT_INVALID_HANDLE);
	RT_ASSERTE(aBuf);
	
	int nRet = ::recv((RT_SOCKET)m_Handle, aBuf, aLen, aFlag);
#ifndef RT_WIN32
  #ifdef RT_MACOS
  #ifndef MachOSupport
	if (nRet == -1 && errno == 35)
		CFM_seterrno(35);
  #else
	if (nRet == -1 && errno == EAGAIN)
		errno = EWOULDBLOCK;
  #endif	//MachOSupport	
  #else
	if (nRet == -1 && errno == EAGAIN)
		errno = EWOULDBLOCK;
  #endif
#else // !RT_WIN32
	if (nRet == SOCKET_ERROR) {
		errno = ::WSAGetLastError();
		nRet = -1;
	}
#endif // RT_WIN32

	return nRet;
}

inline int CRtSocketBase::RecvV(iovec aIov[], DWORD aCount) const 
{
	int nRet;
//	RT_ASSERTE(m_Handle != RT_INVALID_HANDLE);
	RT_ASSERTE(aIov);
	
#ifdef RT_WIN32
	DWORD dwBytesReceived = 0;
	DWORD dwFlags = 0;
	nRet = ::WSARecv((RT_SOCKET)m_Handle,
                      (WSABUF *)aIov,
                      aCount,
                      &dwBytesReceived,
                      &dwFlags,
                      0,
                      0);
	if (nRet == SOCKET_ERROR) {
		errno = ::WSAGetLastError();
		nRet = -1;
	}
	else {
		nRet = (int)dwBytesReceived;
	}
#else // !RT_WIN32
	nRet = ::readv(m_Handle, aIov, aCount);
#endif // RT_WIN32
	return nRet;
}

inline int CRtSocketBase::Send (const char *aBuf, DWORD aLen, int aFlag) const 
{
//	RT_ASSERTE(m_Handle != RT_INVALID_HANDLE);
	RT_ASSERTE(aBuf);

	int nRet = ::send((RT_SOCKET)m_Handle, aBuf, aLen, aFlag);
#ifndef RT_WIN32
  #ifdef RT_MACOS
  #ifndef MachOSupport
	if (nRet == -1 && errno == 35)
		CFM_seterrno(35);
  #else
	if (nRet == -1 && errno == EAGAIN)
		errno = EWOULDBLOCK;
  #endif	//MachOSupport
  #else
	if (nRet == -1 && errno == EAGAIN)
		errno = EWOULDBLOCK;
  #endif		
#else // !RT_WIN32
	if (nRet == SOCKET_ERROR) {
		errno = ::WSAGetLastError();
		nRet = -1;
	}
#endif // RT_WIN32
	return nRet;
}

inline int CRtSocketBase::SendV(const iovec aIov[], DWORD aCount) const 
{
	int nRet;
//	RT_ASSERTE(m_Handle != RT_INVALID_HANDLE);
	RT_ASSERTE(aIov);
	
#ifdef RT_WIN32
	DWORD dwBytesSend = 0;
	nRet = ::WSASend((RT_SOCKET)m_Handle,
                      (WSABUF *)aIov,
                      aCount,
                      &dwBytesSend,
                      0,
                      0,
                      0);
	if (nRet == SOCKET_ERROR) {
		errno = ::WSAGetLastError();
		nRet = -1;
	}
	else {
		nRet = (int)dwBytesSend;
	}
#else // !RT_WIN32
	nRet = ::writev(m_Handle, aIov, aCount);
#endif // RT_WIN32
	return nRet;
}

inline CRtSocketStream::CRtSocketStream()
{
}

inline CRtSocketStream::~CRtSocketStream()
{
	Close();
}

inline int CRtSocketStream::Open(BOOL aReuseAddr)
{
	return CRtSocketBase::Open(PF_INET, SOCK_STREAM, 0, aReuseAddr);
}

/*
inline int CRtSocketStream::Close(RtResult aReason)
{
#ifdef RT_WIN32
	// We need the following call to make things work correctly on
	// Win32, which requires use to do a <CloseWriter> before doing the
	// close in order to avoid losing data.  Note that we don't need to
	// do this on UNIX since it doesn't have this "feature".  Moreover,
	// this will cause subtle problems on UNIX due to the way that
	// fork() works.
	if (m_Handle != RT_INVALID_HANDLE && RT_SUCCEEDED(aReason))
		CloseWriter();
#endif // RT_WIN32

	return CRtSocketBase::Close();
}
*/

inline int CRtSocketBase::CloseWriter()
{
//	RT_ASSERTE(m_Handle != RT_INVALID_HANDLE);
	int nRet = ::shutdown((RT_SOCKET)m_Handle, RT_SD_SEND);

#ifdef RT_WIN32
	if (nRet == SOCKET_ERROR) {
		errno = ::WSAGetLastError();
		nRet = -1;
	}
#endif // RT_WIN32
	return nRet;
}

inline int CRtSocketStream::CloseReader()
{
//	RT_ASSERTE(m_Handle != RT_INVALID_HANDLE);
	int nRet = ::shutdown((RT_SOCKET)m_Handle, RT_SD_RECEIVE);

#ifdef RT_WIN32
	if (nRet == SOCKET_ERROR) {
		errno = ::WSAGetLastError();
		nRet = -1;
	}
#endif // RT_WIN32
	return nRet;
}

inline CRtSocketDgram::CRtSocketDgram()
{
}

inline CRtSocketDgram::~CRtSocketDgram()
{
	Close();
}

inline int CRtSocketDgram::
RecvFrom(char *aBuf, DWORD aLen, CRtInetAddr &aAddr, int aFlag) const 
{
//	RT_ASSERTE(m_Handle != RT_INVALID_HANDLE);

	int nSize = (int)aAddr.GetSize();
	int nRet = ::recvfrom((RT_SOCKET)m_Handle,
						  aBuf,
						  aLen,
						  aFlag,
						  reinterpret_cast<sockaddr *>(const_cast<sockaddr_in *>(aAddr.GetPtr())),
#ifdef RT_WIN32
						  &nSize
#else // !RT_WIN32
						  reinterpret_cast<socklen_t*>(&nSize)
#endif // RT_WIN32
						   );

#ifdef RT_WIN32
	if (nRet == SOCKET_ERROR) {
		errno = ::WSAGetLastError();
		nRet = -1;
	}
#endif // RT_WIN32

	return nRet;
}

inline int CRtSocketDgram::
SendTo(const char *aBuf, DWORD aLen, const CRtInetAddr &aAddr, int aFlag) const 
{
//	RT_ASSERTE(m_Handle != RT_INVALID_HANDLE);

	int nRet = ::sendto((RT_SOCKET)m_Handle,
						  aBuf,
						  aLen,
						  aFlag,
						  reinterpret_cast<const sockaddr *>(aAddr.GetPtr()),
#ifdef RT_WIN32
						  aAddr.GetSize()
#else // !RT_WIN32
						  static_cast<socklen_t>(aAddr.GetSize())
#endif // RT_WIN32
						  );

#ifdef RT_WIN32
	if (nRet == SOCKET_ERROR) {
		errno = ::WSAGetLastError();
		nRet = -1;
	}
#endif // RT_WIN32
	
	return nRet;
}

inline int CRtSocketDgram::
SendVTo(const iovec aIov[], DWORD aCount, const CRtInetAddr &aAddr) const 
{
	int nRet;
//	RT_ASSERTE(m_Handle != RT_INVALID_HANDLE);
	RT_ASSERTE(aIov);
	
#ifdef RT_WIN32
	DWORD dwBytesSend = 0;
	nRet = ::WSASendTo((RT_SOCKET)m_Handle,
                      (WSABUF *)aIov,
                      aCount,
                      &dwBytesSend,
                      0,
                      reinterpret_cast<const sockaddr *>(aAddr.GetPtr()),
                      aAddr.GetSize(),
					  NULL,
					  NULL);
	if (nRet == SOCKET_ERROR) {
		errno = ::WSAGetLastError();
		nRet = -1;
	}
	else {
		nRet = (int)dwBytesSend;
	}
#else // !RT_WIN32
	msghdr send_msg;
	send_msg.msg_iov = (iovec *)aIov;
	send_msg.msg_iovlen = aCount;
	send_msg.msg_name = (struct sockaddr *)aAddr.GetPtr();
	send_msg.msg_namelen = aAddr.GetSize();

	send_msg.msg_control = 0;
	send_msg.msg_controllen = 0;
	send_msg.msg_flags = 0;
	nRet = ::sendmsg(m_Handle, &send_msg, 0);
#endif // RT_WIN32
	return nRet;
}


#endif // !RTSOCKET_H
